﻿using System;
using Nofs.Net.Common.Interfaces.Library;
using biz.ritter.javapi.lang;
using Nofs.Net.nofs.metadata.interfaces;
using Db4objects.Db4o;
using Nofs.Net.Common.Interfaces.Cache;
using Nofs.Net.Common.Interfaces.Domain;
using biz.ritter.javapi.util;
using biz.ritter.javapi.lang.reflect;
using Nofs.Fuse;

namespace Nofs.Net.nofs.Db4o
{
    public class DomainObjectContainer : DomainObjectContainer<object>
    {
        public DomainObjectContainer(
                IStatMapper statMapper, IKeyCache keyCache, IFileObjectFactory fileObjectFactory,
                DomainObjectContainerManager manager, IAttributeAccessor accessor,
                IObjectContainer db, Class type)
            : base(statMapper, keyCache, fileObjectFactory, manager, accessor, db, type)
        {
        }
    }


    public class DomainObjectContainer<T> : IDomainObjectContainerInternal<T>
    {
        private IObjectContainer _db;
        private Class _type;
        private IAttributeAccessor _accessor;
        private DomainObjectContainerManager _manager;
        private bool _init = false;
        private IStatMapper _statMapper;
        private IKeyCache _keyCache;
        private IFileObjectFactory _fileObjectFactory;
        private IFileCacheManager _fileCacheManager;

        public DomainObjectContainer(
                IStatMapper statMapper, IKeyCache keyCache, IFileObjectFactory fileObjectFactory,
                DomainObjectContainerManager manager, IAttributeAccessor accessor,
                IObjectContainer db, Class type)
        {
            _keyCache = keyCache;
            _statMapper = statMapper;
            _db = db;
            _type = type;
            _accessor = accessor;
            _manager = manager;
            _init = false;
            _fileObjectFactory = fileObjectFactory;
        }

        public IKeyIdentifier GetIdentifier(T obj) ////throws Exception 
        {
            return _keyCache.GetByReference(obj);
        }

        //@SuppressWarnings("unchecked")
        public T GetObject(IKeyIdentifier key) //throws Exception 
        {
            return (T)key.getReference();
        }

        public int GetObjectCountForTesting() //throws Exception 
        {
            return GetAllInstances().size();
        }

        //synchronized
        private void Init() //throws Exception 
        {
            if (!_init)
            {
                _init = true;
                foreach (T obj in _db.Query(_type.GetType()))
                {
                    _accessor.setContainerIfAttributeExists((Object)obj, (IDomainObjectContainer)this);
                    _accessor.setContainerManagerIfAttributeExists((Object)obj, (IDomainObjectContainerManager)_manager);
                }
            }
        }

        public void SetFileCacheManager(IFileCacheManager cacheManager)
        {
            _fileCacheManager = cacheManager;
        }

        ////@Override
        public void ObjectRenamed(T sender, String oldName, String newName) //throws Exception 
        {
            _fileCacheManager.Flush(_keyCache.GetByReference(sender).getID());
            _statMapper.Rename(_keyCache.GetByReference(sender).getID(), oldName, newName);
        }

        ////@Override
        public void Remove(T sender) //throws Exception 
        {
            _statMapper.Delete(GetIdentifier(sender).getID());
            _keyCache.Remove(sender);
            _db.Delete(sender);
        }

        ////@Override
        public LinkedList<T> GetAllInstances() //throws Exception 
        {
            Init();
            LinkedList<T> list = new LinkedList<T>();
            foreach(T t in _db.Query<T>(_type.GetType()))
            {
                list.add(t);
            }
            
            return list;
        }

        //@SuppressWarnings("unchecked")
        public Collection<T> GetByExample(T example) //throws Exception 
        {
            IObjectSet results = _db.QueryByExample(example);
            List<T> setupResults = new LinkedList<T>();
            foreach (Object result in results)
            {
                T obj = (T)result;
                _accessor.setContainerIfAttributeExists(result, (IDomainObjectContainer)this);
                _accessor.setContainerManagerIfAttributeExists(result, (IDomainObjectContainerManager)_manager);
                setupResults.add(obj);
            }
            return Collections.unmodifiableList(setupResults);
        }


        public IDomainObjectContainer<T> GetContainer(Class type)
        {
            throw new NotImplementedException();
        }

        private class WeakReference : IWeakReference<T>
        {
            private UUID _id;
            private IKeyCache _keyCache;
            private IAttributeAccessor _accessor;
            private IDomainObjectContainer<T> _container;
            private IDomainObjectContainerManager _manager;

            public WeakReference(
                    UUID id, IKeyCache keyCache, IAttributeAccessor accessor,
                    IDomainObjectContainer<T> container, IDomainObjectContainerManager manager)
            {
                _id = id;
                _keyCache = keyCache;
                _accessor = accessor;
                _container = container;
                _manager = manager;
            }

            //@SuppressWarnings("unchecked")
            public T Get() //throws Exception 
            {
                IKeyIdentifier keyId = _keyCache.GetByID(_id);
                T reference = (T)keyId.getReference();
                _accessor.setContainerIfAttributeExists((Object)reference, (IDomainObjectContainer)_container);
                _accessor.setContainerManagerIfAttributeExists(reference, _manager);
                return reference;
            }
        }

        public IWeakReference<T> GetWeakReference(UUID id) //throws Exception 
        {
            return new WeakReference(id, _keyCache, _accessor, this, (IDomainObjectContainerManager)_manager);
        }

        ////@Override
        public bool IsPersistent(T sender) //throws Exception 
        {
            return GetAllInstances().contains(sender);
        }

        ////@Override
        public void MakePersistent(T sender) //throws Exception 
        {
            if (!IsPersistent(sender))
            {
                _keyCache.Add(new KeyIdentifier<T>(sender));
                _db.Store(sender);
                _db.Commit();
            }
        }

        ////@Override
        public T NewPersistentInstance() //throws Exception 
        {
            T sender;
            try
            {
                sender = (T)_type.newInstance();
            }
            catch (InstantiationException ie)
            {
                throw new System.Exception("Failed to create instance of class type = " + _type.getName()
                    + "\r\nException"
                    + ie.ToString()
                    );
            }
            KeyIdentifier<T> keyId = new KeyIdentifier<T>(sender);
            if (sender is IObjectWithID)
            {
                ((IObjectWithID)sender).setID(keyId.getID());
            }
            _accessor.setContainerIfAttributeExists((Object)sender, (IDomainObjectContainer)this);
            _accessor.setContainerManagerIfAttributeExists((Object)sender, (IDomainObjectContainerManager)_manager);
            _db.Store(sender);
            _db.Commit();
            _keyCache.Add(new KeyIdentifier<T>(sender));
            return sender;
        }

        private Method FindMethod(T sender, String name) //throws Exception 
        {
            foreach (Method method in sender.getClass().getMethods())
            {
                if (method.getName().compareTo(name) == 0 ||
                   (method.getName().startsWith("get") && method.getName().substring(3).compareTo(name) == 0))
                {
                    return method;
                }
            }
            throw new System.Exception("could not find method named '" + name + "'");
        }

        public void CreateAndSaveStatObjects(T sender) //throws Exception 
        {
            Map<String, IFileObjectStat> stats = new HashMap<String, IFileObjectStat>();
            IFileObjectStat parentFileStat = _fileObjectFactory.BuildStat(sender);
            if ((parentFileStat.getMode() & FuseFtypeConstants.TYPE_DIR) > 0)
            {
                Collection<String> childNames = _fileObjectFactory.GetChildNames(sender);
                if (_accessor.isFSRoot(sender))
                {
                    String objectName = _accessor.getNameFromObject(sender);
                    stats.put(objectName, parentFileStat);
                }
                if (childNames.size() == 0)
                {
                    String objectName = _accessor.getNameFromObject(sender);
                    stats.put(objectName, parentFileStat);
                }
                else
                {
                    foreach (String childName in new LinkedList<string>(childNames))
                    {
                        Method method = FindMethod(sender, childName);
                        IFileObjectStat childStat = _fileObjectFactory.BuildStat(sender, method);
                        stats.put(childName, childStat);
                    }
                }
            }
            else if ((parentFileStat.getMode() & FuseFtypeConstants.TYPE_FILE) > 0)
            {
                String objectName = _accessor.getNameFromObject(sender);
                stats.put(objectName, parentFileStat);
            }

            foreach (String name in new LinkedList<string>(stats.keySet()))
            {
                _statMapper.Save(stats.get(name));
            }
            if (!_statMapper.HasStat(_keyCache.GetByReference(sender).getID(), parentFileStat.ParentName()))
            {
                //throw new Exception("stat wasn't saved!");
                _statMapper.Save(parentFileStat);
            }
        }

        public void UpdateStatObjects(T sender, String oldName, String newName) //throws Exception 
        {
            UUID id = _keyCache.GetByReference(sender).getID();
            IFileObjectStat actualStat = _statMapper.Load(id, oldName);
            actualStat.SetParentName(newName);
            _statMapper.Save(actualStat);
        }

        //@Override
        public T NewTransientInstance() //throws Exception 
        {
            T sender = (T)_type.newInstance();
            _accessor.setContainerIfAttributeExists((Object)sender, (IDomainObjectContainer)this);
            _accessor.setContainerManagerIfAttributeExists((Object)sender, (IDomainObjectContainerManager)_manager);
            return sender;
        }

        //@Override
        public void ObjectChanged(T sender) //throws Exception 
        {
            if (!_statMapper.HasStat(_keyCache.GetByReference(sender).getID()))
            {
                CreateAndSaveStatObjects(sender);
            }
            _db.Store(sender);
            _db.Commit();
        }

    }

}
